#include "../../public/_public.h"
#include "../../public/_ftp.h"

using namespace idc;

cftpclient ftp;  //这里要使用全局变量保证程序在调用EXIT函数退出时会调用析构函数释放资源
cpactive pactive;
clogfile logfile;

void _help(); //用来显示帮助信息
void Exit(int sig);  //信号处理函数
bool _xmlbuffer(const char *xmlbuffer);  //用来解析xml格式的配置参数读取参数中xml格式的配置参数
bool loadfile(); //将匹配文件的结果放到容器里面

struct st_argv{
    char host[31];  //远程ftp服务器的ip地址与端口
    int mode;     //与ftp服务器的连接方式，1-被动模式  2-主动模式 其余默认按被动处理
    char username[31]; //用户名
    char password[31]; //密码
    char localpath[256]; //本地目录
    char remotepath[256]; //远程目录
    char matchname[31];  //文件匹配规则
    int ptype; //  下载后服务端文件的处理方式  1-不做处理 2-删除 3-移动到备份目录中
    char remotepathbak[256]; //备份目录
    char okfilename[256];   //存储已经成功下载文件信息的文件
    bool checkmtime; //是否检查文件时间，1-是  0-否
    int timeout; //超时时间
    char pname[31]; //进程名
}starg;

struct st_fileinfo  //存放文件信息的结构体
{
    string filename; //文件名
    string mtime; //文件时间
    st_fileinfo() =default;
    st_fileinfo(const string &infilename,const string &in_mtime):filename(infilename),mtime(in_mtime){}
    void clear(){filename.clear();mtime.clear();}
};

map<string,string> getfilelist;  //存储所有已经下载文件的下载/修改时间，用来和下一次下载的文件进行对比
list<st_fileinfo> vformfile;
list<st_fileinfo> vtook; //本次不需要下载的文件
list<st_fileinfo> vdownload; //本次需要下载的文件

bool loadokfile(); //将已经下载过的文件信息加载到容器中
bool comparefile(); //比较文件，将需要下载的文件放到容器中
bool writetookfile();// 把容器vtook中的数据写入starg.okfilename文件，覆盖之前的旧starg.okfilename文件。
bool appendokfile(st_fileinfo& aa); //把容器vdownload中的数据追加到starg.okfilename文件中

int main(int argc,char *argv[])
{   
    if(argc != 3)
    {
        _help();
    }
    closeallsign();
    signal(SIGINT,Exit); signal(SIGTERM,Exit);
    if(logfile.open(argv[1])==false)
    {
        printf("logfile.open(%s) failed.\n",argv[1]);
    } 
    if(  _xmlbuffer(argv[2]) == false)   //解析xml格式的配置参数
    {
        printf("xmlbuffer(%s) failed.\n",argv[2]);
        return -1;
    }
    
    printf("%s\n%d\n%s\n%s\n%s\n%s\n%s\n%d\n",starg.host,starg.mode,starg.username,starg.password,starg.localpath,starg.remotepath,starg.matchname,starg.ptype);

    //登录ftp服务器
    if(ftp.login(starg.host,starg.username,starg.password,starg.mode) == false)
    {
        logfile.write("ftp.login(%s,%s,%s,%d) failed.\n%s\n",starg.host,starg.username,starg.password,starg.mode,ftp.response());
        return -1;
    }
    logfile.write("ftp.login(%s,%s,%s,%d) ok.\n",starg.host,starg.username,starg.password,starg.mode);
    //切换到工作目录下
    if(ftp.chdir(starg.remotepath)==false)
    {
        logfile.write("ftp.chdir(%s) failed.\n%s\n",starg.remotepath,ftp.response());
        return -1;
    }
    //按照匹配规则匹配文件
    if(ftp.nlst(".",idc::sformat("/tmp/nlst/ftpgetfiles_%d",getpid()))==false)
    {
        logfile.write("ftp.nlst() failed.\n%s\n",ftp.response());
        return -1;
    }

    pactive.update();

    //将匹配文件的结果放到容器里面
    if(loadfile()==false)
    {
        logfile.write("loadfile() failed.\n");
        return -1;
    }

    if(starg.ptype==1)
    {
        //加载已经被下载过的文件
        loadokfile();
        //将getfilelist容器与vformfile容器进行对比
        comparefile();
        //将vtookfile容器里面的文件名写到okfilename里面,覆盖上次内容
        writetookfile();
    }
    else vformfile.swap(vdownload);
    pactive.update();

    //将容器里面的文件名写到日志文件里面,仅为测试使用
    // for(auto aa:vformfile)
    // {
    //    logfile.write("%s\n",aa.filename.c_str());
    // }

    string strremotefile,strlocalfile;//  用来存放全路径的文件名
    for(auto aa:vdownload)
    {
        sformat(strremotefile,"%s/%s",starg.remotepath,aa.filename.c_str());
        sformat(strlocalfile,"%s/%s",starg.localpath,aa.filename.c_str());
        cout<<strremotefile<<" "<<strlocalfile<<endl;
        if(ftp.get(strremotefile,strlocalfile)==false)
        {
            logfile.write("ftp.get(%s,%s) failed.\n%s\n",strremotefile.c_str(),(starg.localpath+aa.filename).c_str(),ftp.response()
            );
            return -1;
        }
        pactive.update();
        logfile.write("ftp.get(%s,%s) ok.\n",aa.filename.c_str(),starg.localpath+aa.filename);
        if(starg.ptype==1)
        {
           appendokfile(aa);
        }
        if(starg.ptype==2)
        {
            if(ftp.deletefile(strremotefile)==false)
            {
                logfile.write("ftp.deletefile(%s) failed.\n%s\n",strremotefile.c_str(),ftp.response());
                return -1;
            }
        }
        if(starg.ptype==3)
        {
            string strremotefile=sformat("%s/%s",starg.remotepath,aa.filename.c_str());
            if(ftp.rename(strremotefile,aa.filename)==false)
            {
                logfile.write("ftp.rename(%s,%s) failed.\n%s\n",strremotefile.c_str(),aa.filename.c_str(),ftp.response());
            }
        }
    }

    return 0;
}

void _help() 
{
    printf("\n");
    printf("Using: /root/mylib/project/tools/bin/ftpgetfiles logfilepath xmlbuffer\n");

    printf("Example:/root/mylib/project/tools/bin/procctl 30 /root/mylib/project/tools/bin/ftpgetfiles /log/idc/ftpgetfiles_test.log"\
    "\" <host>47.103.204.186</host><mode>1</mode>"\
    "<username>ftpuser</username><password>ba161754</password>"\
    "<remotepath>/ftp/work/data</remotepath><localpath>/tmp/idc/data</localpath>"
    "<matchname>SURF_ZH*.XML,SURF_ZH*.CSV</matchname>\"\n\n");
    printf("本程序是通用的功能模块，用于把远程ftp服务端的文件下载到本地目录。\n");
    printf("logfilename是本程序运行的日志文件。\n");
    printf("xmlbuffer为文件下载的参数，如下：\n");
    printf("<host>192.168.150.128:21</host> 远程服务端的IP和端口。\n");
    printf("<mode>1</mode> 传输模式，1-被动模式，2-主动模式，缺省采用被动模式。\n");
    printf("<username>wucz</username> 远程服务端ftp的用户名。\n");
    printf("<password>oraccle</password> 远程服务端ftp的密码。\n");
    printf("<remotepath>/tmp/idc/surfdata</remotepath> 远程服务端存放文件的目录。\n");
    printf("<localpath>/idcdata/surfdata</localpath> 本地文件存放的目录。\n");
    printf("<matchname>SURF_ZH*.XML,SURF_ZH*.CSV</matchname> 待下载文件匹配的规则。"\
              "不匹配的文件不会被下载，本字段尽可能设置精确，不建议用*匹配全部的文件。\n");
    printf("<ptype>1</ptype> 文件下载成功后，远程服务端文件的处理方式："\
              "1-什么也不做；2-删除；3-备份，如果为3，还要指定备份的目录。\n");
    printf("<remotepathbak>/tmp/idc/surfdatabak</remotepathbak> 文件下载成功后，服务端文件的备份目录，"\
              "此参数只有当ptype=3时才有效。\n");
    printf("<okfilename>/idcdata/ftplist/ftpgetfiles_test.xml</okfilename> 已下载成功文件名清单，"\
              "此参数只有当ptype=1时才有效。\n");
    printf("<checkmtime>true</checkmtime> 是否需要检查服务端文件的时间，true-需要，false-不需要，"\
              "此参数只有当ptype=1时才有效，缺省为false。\n");
    printf("<timeout>30</timeout> 下载文件超时时间，单位：秒，视文件大小和网络带宽而定。\n");
    printf("<pname>ftpgetfiles_test</pname> 进程名，尽可能采用易懂的、与其它进程不同的名称，方便故障排查。\n\n\n");
}

void Exit(int sig)
{
    printf("程序退出，信号：%d\n", sig);
}

bool _xmlbuffer(const char* xmlbuffer)
{
    memset(&starg, 0, sizeof(starg));
    getxmlbuffer(xmlbuffer,"host",starg.host,31);
    if(starg.host==nullptr)
    {
        logfile.write("host is null");
        return false;
    }
    getxmlbuffer(xmlbuffer,"mode",starg.mode);
    if(starg.mode!=2)
    {
        starg.mode=1;
    }
    getxmlbuffer(xmlbuffer,"username",starg.username,31);
    if(starg.username==nullptr)
    {
        logfile.write("username is null");
        return false;
    }
    getxmlbuffer(xmlbuffer,"password",starg.password,31);
    if(starg.password==nullptr)
    {
        logfile.write("password is null");
        return false;
    }
    getxmlbuffer(xmlbuffer,"remotepath",starg.remotepath,256);
    {
        if(starg.remotepath==nullptr)
        {
            logfile.write("remotepath is null");
            return false;
        }
    }
    getxmlbuffer(xmlbuffer,"localpath",starg.localpath,256);
    {
        if(starg.localpath==nullptr)
        {
            logfile.write("localpath is null");
            return false;
        }
    }
    getxmlbuffer(xmlbuffer,"matchname",starg.matchname,256);
    if(starg.matchname==nullptr)
    {
        logfile.write("matchname is null");
        return false;
    }
    getxmlbuffer(xmlbuffer,"ptype",starg.ptype);
    if(starg.ptype!=1 && starg.ptype!=2 && starg.ptype!=3)
    {
        logfile.write("ptype is error");
        return false;
    }
    if (starg.ptype==3)
    {
        getxmlbuffer(xmlbuffer,"remotepathbak",starg.remotepathbak,256);
    }
    getxmlbuffer(xmlbuffer,"okfilename",starg.okfilename,256);
    if(starg.okfilename==nullptr)
    {
        logfile.write("okfilename is null");
        return false;
    }
    getxmlbuffer(xmlbuffer,"okfilename",starg.okfilename,256);
    if(starg.okfilename==nullptr)
    {
        logfile.write("okfilename is null");
        return false;
    }
    getxmlbuffer(xmlbuffer,"timeout",starg.timeout);
    if(starg.timeout<=0)
    {
        logfile.write("timeout is error");
        return false;
    }
    getxmlbuffer(xmlbuffer,"checkmtime",starg.checkmtime);
}

bool loadfile()
{
    idc::cifile ifile;
    vformfile.clear();
    if(ifile.open(idc::sformat("/tmp/nlst/ftpgetfiles_%d",getpid()))==false)
    {
        logfile.write("open /tmp/nlst/ftpgetfiles_%d error",getpid());
        return false;
    }
    string strfilename;
    while(true)
    {
        if(ifile.readline(strfilename)==false) break;
        if(ftp.mtime(strfilename)==false)
        {
            logfile.write("mtime %s error",strfilename.c_str());
        }
        vformfile.emplace_back(strfilename,ftp.m_mtime);
    }
}

bool loadokfile()
{
    getfilelist.clear();
    cifile ifile;
    if(ifile.open(starg.okfilename)==false) return true;
    string strbuffer;
    struct st_fileinfo stfile;
    while(true)
    {
        if(ifile.readline(strbuffer)==false) break;
        stfile.clear();
        getxmlbuffer(strbuffer,"filename",stfile.filename);
        getxmlbuffer(strbuffer,"mtime",stfile.mtime);
        getfilelist[stfile.filename]=stfile.mtime;
    }
    return true;
}

bool comparefile()
{
    vtook.clear();
    vdownload.clear();
    for(auto& st:vformfile)
    {
        auto it=getfilelist.find(st.filename);
        if(it!=getfilelist.end()) 
        {
            if(starg.checkmtime==true)
            {
                if(st.mtime==it->second) vtook.emplace_back(st);
                else vdownload.emplace_back(st);
            }
            else vtook.emplace_back(st);
        }
        else vdownload.emplace_back(st);
    }
    return true;
}

bool writetookfile()
{
    cofile ofile;
    if(ofile.open(starg.okfilename)==false)
    {
        logfile.write("open %s error\n",starg.okfilename);
        return false;
    }
    ofile.writeline("%s",starg.okfilename);
    for (auto &aa:vtook)
        ofile.writeline("<filename>%s</filename><mtime>%s</mtime>\n",aa.filename.c_str(),aa.mtime.c_str());
    ofile.closeandrename();
    return true;
}

bool appendokfile(st_fileinfo& aa)
{
    cofile ofile;
    if(ofile.open(starg.okfilename,false,ios::app)==false)
    {
        logfile.write("open %s error\n",starg.okfilename);
        return false;
    }
    ofile.writeline("<filename>%s</filename><mtime>%s</mtime>\n",aa.filename.c_str(),aa.mtime.c_str());
    ofile.closeandrename();
    return true;
}